/*
 * \brief  PS/2 mouse protocol handler
 * \author Norman Feske
 * \date   2007-10-09
 */

/*
 * Copyright (C) 2007-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _PS2_MOUSE_H_
#define _PS2_MOUSE_H_

#include <base/printf.h>
#include <input/keycodes.h>

#include "event_queue.h"
#include "input_driver.h"

class Ps2_mouse : public Input_driver
{
	enum Command
	{
		CMD_ENABLE_STREAM  = 0xf4,
		CMD_DISABLE_STREAM = 0xf5,
		CMD_SET_DEFAULTS   = 0xf6,
	};

	enum Return
	{
		RET_ACK      = 0xfa,
		RET_NAK      = 0xfe,
		RET_ERROR    = 0xff,
	};

	enum Flag
	{
		FLAG_BTN_LEFT   = 0x01,
		FLAG_BTN_RIGHT  = 0x02,
		FLAG_BTN_MIDDLE = 0x04,
		FLAG_X_SIGN     = 0x10,
		FLAG_Y_SIGN     = 0x20,
		FLAG_X_OVER     = 0x40,
		FLAG_Y_OVER     = 0x80,
	};

	enum {
		LEFT   = 0,
		RIGHT  = 1,
		MIDDLE = 2,
		NUM_BUTTONS,
	};

	private:

		static const bool verbose = false;

		Serial_interface &_aux;
		Event_queue      &_ev_queue;

		bool              _button_state[NUM_BUTTONS];

		enum { PS2_MOUSE_PACKET_LEN = 3 };

		unsigned char _packet[PS2_MOUSE_PACKET_LEN];
		int           _packet_idx;

		/**
		 * Generate mouse button event on state changes
		 *
		 * Depending on the old and new state, this function
		 * posts press or release events for the mouse buttons
		 * to the event queue. Note that the old state value
		 * gets set to the new state.
		 */
		void _button_event(bool *old_state, bool new_state, int key_code)
		{
			if (*old_state == new_state) return;

			if (verbose)
				Genode::printf("post %s, key_code = %d\n", new_state ? "PRESS" : "RELEASE", key_code);

			_ev_queue.add(Input::Event(new_state ? Input::Event::PRESS
			                                     : Input::Event::RELEASE,
			                           key_code, 0, 0, 0, 0));
			*old_state = new_state;
		}

	public:

		Ps2_mouse(Serial_interface &aux, Event_queue &ev_queue)
		: _aux(aux), _ev_queue(ev_queue), _packet_idx(0)
		{
			reset();
		}

		void reset()
		{
			_aux.write(CMD_SET_DEFAULTS);
			if (_aux.read() != RET_ACK)
				PWRN("Could not set defaults");

			_aux.write(CMD_ENABLE_STREAM);
			if (_aux.read() != RET_ACK)
				PWRN("Could not enable stream");
		}


		/****************************
		 ** Input driver interface **
		 ****************************/

		void handle_event()
		{
			_packet[_packet_idx++] = _aux.read();
			if (_packet_idx < PS2_MOUSE_PACKET_LEN)
				return;

			/* decode packet and feed event queue */
			int rel_x = _packet[1], rel_y = _packet[2];

			/* shortcut for packet header */
			int ph = _packet[0];

			/* sign extend motion values */
			rel_x |= (ph & FLAG_X_SIGN) ? ~0xff : 0;
			rel_y |= (ph & FLAG_Y_SIGN) ? ~0xff : 0;

			/* discard motion values on overflow */
			rel_x &= (ph & FLAG_X_OVER) ? 0 : ~0;
			rel_y &= (ph & FLAG_Y_OVER) ? 0 : ~0;

			/* generate motion event */
			if (rel_x || rel_y) {

				/* mirror y axis to make the movement correspond to screen coordinates */
				rel_y = -rel_y;

				if (verbose)
					Genode::printf("post MOTION, rel_x = %d, rel_y = %d\n", rel_x, rel_y);

				_ev_queue.add(Input::Event(Input::Event::MOTION,
				                           0, 0, 0, rel_x, rel_y));
			}

			/* detect changes of mouse-button state and post corresponding events */
			_button_event(&_button_state[LEFT],   ph & FLAG_BTN_LEFT,   Input::BTN_LEFT);
			_button_event(&_button_state[RIGHT],  ph & FLAG_BTN_RIGHT,  Input::BTN_RIGHT);
			_button_event(&_button_state[MIDDLE], ph & FLAG_BTN_MIDDLE, Input::BTN_MIDDLE);

			/* start new packet */
			_packet_idx = 0;
		}

		bool event_pending() { return _aux.data_read_ready(); }
};

#endif /* _PS2_MOUSE_H_ */
